package edu.bbu.license.client.android.services.transfer.worker;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import edu.bbu.license.client.android.communication.exceptions.DirectoryOperationException;
import edu.bbu.license.client.android.communication.exceptions.FileDownloadException;
import edu.bbu.license.client.android.communication.exceptions.FileUploadException;
import edu.bbu.license.client.android.communication.exceptions.NoConnectionException;
import edu.bbu.license.client.android.communication.exceptions.TransferInterruptedException;
import edu.bbu.license.client.android.communication.factory.CLTFactory;
import edu.bbu.license.client.android.communication.model.DirectoryPOJO;
import edu.bbu.license.client.android.communication.model.FilePOJO;
import edu.bbu.license.client.android.communication.services.DirectoryCLTServices;
import edu.bbu.license.client.android.communication.services.FileCLTServices;
import edu.bbu.license.client.android.services.BaseService;
import edu.bbu.license.client.android.services.exceptions.IdentificationException;
import edu.bbu.license.client.android.services.exceptions.NetworkException;
import edu.bbu.license.client.android.services.exceptions.UnknownException;
import edu.bbu.license.client.android.services.file.FileServiceProvider;
import edu.bbu.license.client.android.services.file.model.LocalFile;
import edu.bbu.license.client.android.services.file.model.ServerFile;
import edu.bbu.license.client.android.services.file.worker.FileService;
import edu.bbu.license.client.android.services.transfer.listener.TransferListener;
import edu.bbu.license.client.android.services.transfer.model.SimpleTransferObject;
import edu.bbu.license.client.android.utility.Logger;

public class SimpleTransferService extends BaseService implements TransferService {

	private FileCLTServices fileServices = CLTFactory.getCLTFactory(CLTFactory.THRIFT_FACTORY).getFileCLTServices();
	private DirectoryCLTServices directoryServices =  CLTFactory.getCLTFactory(CLTFactory.THRIFT_FACTORY).getDirectoryCLTServices();	
	
	private LinkedList<SimpleTransferObject> transferList = new LinkedList<SimpleTransferObject>();
	private LinkedList<TransferListener> listenerList = new LinkedList<TransferListener>();
	
	private FileService syncFileServices = new FileServiceProvider().getFileService();
	
	@Override
	public void startUpload(LocalFile localFile, ServerFile uploadDirectory) throws IdentificationException, NetworkException, UnknownException {
		if(isUserLoggedIn()) {
			File regularFile = localFile.getRegularFile();
			
			try {
				if(regularFile.isDirectory()) {
					DirectoryPOJO directoryPOJO = new DirectoryPOJO();
					directoryPOJO.setOwner(getCurrentlyLoggedInUser());
					directoryPOJO.setDirectoryName(localFile.getName());
					directoryPOJO.setParrentDirectory(uploadDirectory.getAbsolutePath() + "\\");
					
					try {
						directoryServices.createDirectory(directoryPOJO);
					} catch (DirectoryOperationException e) { }
					
					for (File file : regularFile.listFiles()) {				
						startUpload(new LocalFile(file), uploadDirectory.getSuccessor(regularFile.getName()));
					}
				} else {
					SimpleTransferObject transferObserver = new SimpleTransferObject();
					transferObserver.setStatus(SimpleTransferObject.UPLOAD);
					transferObserver.setFileName(localFile.getName());
					transferObserver.setFileSize(localFile.getRegularFile().length());
					
					transferList.addFirst(transferObserver);
					notifyTransferListeners(transferObserver);
					
					FilePOJO filePOJO = new FilePOJO();
					filePOJO.setOwner(getCurrentlyLoggedInUser());
					filePOJO.setFileName(localFile.getName());
					filePOJO.setDirectoryName(uploadDirectory.getAbsolutePath() + "\\");
					
					filePOJO.setFileInputStream(new FileInputStream(localFile.getRegularFile()));
					fileServices.uploadFile(filePOJO, transferObserver);
				}
			} catch (NoConnectionException e) {
				throw new NetworkException("Uploading failed, network error.", e);
			} catch (FileUploadException e) {
				throw new NetworkException("Uploading failed, network error or upload aborted.", e);
			} catch (TransferInterruptedException e) {
				FilePOJO filePOJO = new FilePOJO();
				filePOJO.setOwner(getCurrentlyLoggedInUser());
				filePOJO.setFileName(localFile.getName());
				filePOJO.setDirectoryName(uploadDirectory.getAbsolutePath() + "\\");
				
				try {
					filePOJO.setVersion(fileServices.getLastFileVersionNumber(filePOJO));
					fileServices.deleteFile(filePOJO);
				} catch (Exception ex) { }
				
				throw new NetworkException("Uploading failed, beacause it was aborted by the user.", e);
			}
			catch (Exception e) {
				throw new UnknownException("Uploading failed, unknown error.", e);
			}
		} else {
			throw new IdentificationException("Uploading failed, user isn't logged in.");
		}
	}

	@Override
	public void startDownload(ServerFile serverFile, LocalFile downloadDirectory) throws IdentificationException, NetworkException, UnknownException {
		if(isUserLoggedIn()) {
			try {
				if(serverFile.isDirectory()) {
					File newLocalDirectory = new File(downloadDirectory.getAbsolutePath(), serverFile.getName());
					newLocalDirectory.mkdir();
					
					DirectoryPOJO directoryPOJO = new DirectoryPOJO();
					directoryPOJO.setOwner(getCurrentlyLoggedInUser());
					directoryPOJO.setDirectoryName(serverFile.getName());
					directoryPOJO.setParrentDirectory(serverFile.getRegularPath());
					
					ArrayList<String> fileNames = fileServices.getFilesFromDirectory(directoryPOJO);
					ArrayList<String> directoryNames = directoryServices.getDirectoriesFromDirectory(directoryPOJO);
					
					for (String directory : directoryNames) {
						ServerFile newServerFile = new ServerFile(serverFile.getAbsolutePath(), directory, ServerFile.DIRECTORY);
						startDownload(newServerFile, new LocalFile(newLocalDirectory));
					}
					
					for (String file : fileNames) {
						ServerFile newServerFile = new ServerFile(serverFile.getAbsolutePath(), file, ServerFile.FILE);
						startDownload(newServerFile, new LocalFile(newLocalDirectory));
					}
				} else {
					FilePOJO filePOJO = new FilePOJO();
					filePOJO.setOwner(getCurrentlyLoggedInUser());
					filePOJO.setFileName(serverFile.getRegularName());
					filePOJO.setDirectoryName(serverFile.getRegularPath());
					
					if(serverFile.isFileVersion()){
						filePOJO.setVersion(serverFile.getVersion());						
					} else if(serverFile.isFile()) {
						filePOJO.setVersion(fileServices.getLastFileVersionNumber(filePOJO));
					}
					
					SimpleTransferObject transferObserver = new SimpleTransferObject();
					transferObserver.setStatus(SimpleTransferObject.DOWNLOAD);
					transferObserver.setFileName(serverFile.getRegularName());
					transferObserver.setFileSize(fileServices.getFileSize(filePOJO));
					
					transferList.addFirst(transferObserver);
					notifyTransferListeners(transferObserver);
					
					File localFile = new File(downloadDirectory.getAbsolutePath(), serverFile.getRegularName());
					
					filePOJO.setFileOutputStream(new FileOutputStream(localFile));
					fileServices.downloadFile(filePOJO, transferObserver);
					
					localFile.setLastModified((fileServices.getFileUploadDate(filePOJO) / 1000) * 1000);
				}
			} catch (NoConnectionException e) {
				throw new NetworkException("Download failed, network error.", e);
			} catch (FileDownloadException e) {
				throw new NetworkException("Download failed, network error or download aborted.", e);
			} catch (TransferInterruptedException e) {
				File corruptedFile = new File(downloadDirectory.getAbsolutePath(), serverFile.getRegularName());
				corruptedFile.delete();
				
				throw new NetworkException("Download failed, beacause it was aborted by the user.", e);
			} catch (Exception e) {
				throw new UnknownException("Download failed, unknown error.", e);
			}
		} else {
			throw new IdentificationException("Download failed, user isn't logged in.");
		}
	}
	
	public void startSynchronization() {
		int syncType = isOneWaySyncTypeSet() ? ONE_WAY_SYNC : TWO_WAY_SYNC;
		
		try {
			LocalFile localSyncDir = new LocalFile(new File(getLocalSyncDirectory()));
			DirectoryPOJO servercDir = directoryServices.getSyncDirectory(getCurrentlyLoggedInUser());
			ServerFile serverSyncDir = new ServerFile(servercDir.getParrentDirectory(), servercDir.getDirectoryName(), ServerFile.DIRECTORY);
			
			switch(syncType)
			{
				case ONE_WAY_SYNC:
				{
					synchronizeDownload(localSyncDir, serverSyncDir);
					synchronizeDelete(localSyncDir, serverSyncDir);
				} break;
				
				case TWO_WAY_SYNC:
				{
					synchronizeDownload(localSyncDir, serverSyncDir);
					synchronizeUpload(localSyncDir, serverSyncDir);
				} break;
			}
		} catch (Exception e) { }
	}
	
	private void synchronizeDownload(LocalFile localDirectory, ServerFile serverDirectory) throws IdentificationException, NetworkException, UnknownException {		
		List<LocalFile> localFiles = syncFileServices.getLocalDirectoryContent(localDirectory);
		List<ServerFile> serverFiles = syncFileServices.getServerDirectoryContent(serverDirectory);
		
		for (ServerFile serverFile : serverFiles) {
			if(serverFile.isDirectory()) {
				
				boolean existLocaly = false;
				LocalFile existingLocalFile = null;
				
				for(LocalFile localFile : localFiles) {
					if(localFile.isDirectory()) {
						if(serverFile.getName().equals(localFile.getName())) {
							existLocaly = true;
							existingLocalFile = localFile;
							break;
						}
					}
				}
				
				if(existLocaly) {
					synchronizeDownload(existingLocalFile, serverFile);
				} else {
					startDownload(serverFile, localDirectory);
				}
			} else {
				boolean existLocaly = false;
				LocalFile existingLocalFile = null;
				
				for(LocalFile localFile : localFiles) {
					if(localFile.isFile()) {
						if(serverFile.getName().equals(localFile.getName())) {
							existingLocalFile = localFile;
							existLocaly = true;
							break;
						}
					}
				}
				
				if(!existLocaly) {
					startDownload(serverFile, localDirectory);
				} else {
					try {
						FilePOJO filePOJO = new FilePOJO();
						filePOJO.setOwner(getCurrentlyLoggedInUser());
						filePOJO.setFileName(serverFile.getRegularName());
						filePOJO.setDirectoryName(serverFile.getRegularPath());
						filePOJO.setVersion(fileServices.getLastFileVersionNumber(filePOJO));
						
						long fileUploadDate = (fileServices.getFileUploadDate(filePOJO) / 1000) * 1000;
						long lastModifiedDate = (existingLocalFile.getRegularFile().lastModified() / 1000) * 1000;
						
						if(fileUploadDate > lastModifiedDate) {
							startDownload(serverFile, localDirectory);
							existingLocalFile.getRegularFile().setLastModified(fileUploadDate);
						}
					} catch (Exception e) {
						throw new UnknownException("Synchronisation error.", e);
					} 
				}
			}
		}
		
	}
	
	private void synchronizeDelete(LocalFile localDirectory, ServerFile serverDirectory) throws IdentificationException, NetworkException, UnknownException {
		List<LocalFile> localFiles = syncFileServices.getLocalDirectoryContent(localDirectory);
		List<ServerFile> serverFiles = syncFileServices.getServerDirectoryContent(serverDirectory);
		
		for (LocalFile localFile : localFiles) {
			if(localFile.isDirectory()) {
				boolean existOnServer = false;
				ServerFile existingServerFile = null;
				
				for(ServerFile serverFile : serverFiles) {
					if(serverFile.isDirectory()) {
						if(localFile.getName().equals(serverFile.getName())) {
							existOnServer = true;
							existingServerFile = serverFile;
							break;
						}
					}
				}
				
				if(existOnServer) {
					synchronizeDelete(localFile, existingServerFile);
				} else {
					syncFileServices.deleteLocalFile(localFile);
				}
			} else {
				boolean existOnServer = false;
				
				for(ServerFile serverFile : serverFiles) {
					if(serverFile.isFile()) {
						if(localFile.getName().equals(serverFile.getName())) {
							existOnServer = true;
							break;
						}
					}
				}
				
				if(!existOnServer) {
					syncFileServices.deleteLocalFile(localFile);
				}
			}
		}
	}
	
	private void synchronizeUpload(LocalFile localDirectory, ServerFile serverDirectory) throws IdentificationException, NetworkException, UnknownException {
		List<LocalFile> localFiles = syncFileServices.getLocalDirectoryContent(localDirectory);
		List<ServerFile> serverFiles = syncFileServices.getServerDirectoryContent(serverDirectory);
		
		for (LocalFile localFile : localFiles) {
			if(localFile.isDirectory()) {
				
				boolean existOnServer = false;
				ServerFile existingServerFile = null;
				
				for(ServerFile serverFile : serverFiles) {
					if(serverFile.isDirectory()) {
						if(localFile.getName().equals(serverFile.getName())) {
							existOnServer = true;
							existingServerFile = serverFile;
							break;
						}
					}
				}
				
				if(existOnServer) {
					synchronizeUpload(localFile, existingServerFile);
				} else {
					startUpload(localFile, serverDirectory);
				}
			} else {
				boolean existOnServer = false;
				ServerFile existingServerFile = null;
				
				for(ServerFile serverFile : serverFiles) {
					if(serverFile.isFile()) {
						if(localFile.getName().equals(serverFile.getName())) {
							existingServerFile = serverFile;
							existOnServer = true;
							break;
						}
					}
				}
				
				if(!existOnServer) {
					startUpload(localFile, serverDirectory);
				} else {
					try {
						FilePOJO filePOJO = new FilePOJO();
						filePOJO.setOwner(getCurrentlyLoggedInUser());
						filePOJO.setFileName(existingServerFile.getRegularName());
						filePOJO.setDirectoryName(existingServerFile.getRegularPath());
						filePOJO.setVersion(fileServices.getLastFileVersionNumber(filePOJO));
						
						long fileUploadDate = (fileServices.getFileUploadDate(filePOJO) / 1000) * 1000;
						long lastModifiedDate = (localFile.getRegularFile().lastModified() / 1000) * 1000;
						
						if(fileUploadDate < lastModifiedDate) {
							fileServices.deleteFile(filePOJO);
							startUpload(localFile, serverDirectory);
							filePOJO.setVersion(fileServices.getLastFileVersionNumber(filePOJO));
							localFile.getRegularFile().setLastModified((fileServices.getFileUploadDate(filePOJO) / 1000) * 1000);
						}
					} catch (Exception e) {
						throw new UnknownException("Synchronisation error.", e);
					} 
				}
			}
		}
	}
	

	private synchronized void notifyTransferListeners(SimpleTransferObject transferObject) {
		for (TransferListener listener : listenerList) {
			listener.onNewTransferStart(transferObject);
		}
	}
	
	public synchronized void removeTransfer(SimpleTransferObject transferObject) {
		transferList.remove(transferObject);
		
		for (TransferListener listener : listenerList) {
			listener.onNewTransferRemove(transferObject);
		}
	}
	
	@Override
	public synchronized void addTransferListener(TransferListener transferListener) {
		listenerList.add(transferListener);
	}
	
	@Override
	public synchronized void removeTransferListener(TransferListener transferListener) {
		listenerList.remove(transferListener);		
	}
	
	@Override
	public List<SimpleTransferObject> getTransfers() {
		return new LinkedList<SimpleTransferObject>(transferList);
	}
}
